/*
 * SelectInterrupter2.cpp
 *
 *  Created on: Jun 5, 2013
 *      Author: Lucifer
 */

#include "../pch.h"
#include "SelectInterrupter.h"

namespace BFX
{

////////////////////////////////////////////////////////////////////////////////
// SelectInterrupter

#if defined(_WIN32) || defined(__CYGWIN__)

/**
 * Initializes a new instance of BFX::SelectInterrupter.
 */
SelectInterrupter::SelectInterrupter()
{
	// This helper class to make sure the file descriptor will be close correctly even exception rises.
	struct SocketHolder
	{
		SocketHolder(SOCKET fd) : m_fd(fd) {}
		~SocketHolder() { if (m_fd != INVALID_SOCKET) closesocket(m_fd); }
		SOCKET Orphan () { SOCKET ret = m_fd; m_fd = INVALID_SOCKET; return ret; }
		SOCKET	m_fd;
	};

	REFSocket::StaticInitialize();

	for (;;)
	{
		int nOption;
		int nRetVal;
		u_long nNonblocking;

		//
		// Creates our interrupt listener.
		//
		SocketHolder listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (listener.m_fd == INVALID_SOCKET)
			break;

		// set the socket to waiting reusable.
		nOption = 1;
		nRetVal = setsockopt(listener.m_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&nOption, sizeof(int));
		BFX_ASSERT(nRetVal != SOCKET_ERROR);

		SOCKADDR_IN addr;
		SOCKLEN_T nAddrLen = sizeof(addr);
		memset(&addr, 0, sizeof(addr));
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = inet_addr("127.0.0.1");
		addr.sin_port = 0;

		nRetVal = bind(listener.m_fd, (const SOCKADDR*) &addr, nAddrLen);
		if (nRetVal == SOCKET_ERROR)
			break;

	    if (getsockname(listener.m_fd, (SOCKADDR*)&addr, &nAddrLen) == SOCKET_ERROR)
			break;

		// Some broken firewalls on Windows will intermittently cause getsockname to return 0.0.0.0 when the socket is
		// actually bound to 127.0.0.1.
		// We explicitly specify the target address here to work around this problem.
		addr.sin_addr.s_addr = inet_addr("127.0.0.1");

		nRetVal = listen(listener.m_fd, SOMAXCONN);
		if (nRetVal == SOCKET_ERROR)
			break;

		//
		// Creates our interrupt client.
		//
		SocketHolder sender = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (sender.m_fd == INVALID_SOCKET)
			break;

		nRetVal = connect(sender.m_fd, (const SOCKADDR*) &addr, nAddrLen);
		if (nRetVal == SOCKET_ERROR)
			break;

		//
		// Creates our interrupt server.
		//
		SocketHolder receiver = accept(listener.m_fd, 0, 0);
		if (receiver.m_fd == INVALID_SOCKET)
			break;

		//
		// More settings
		//
		nNonblocking = 1;
		nRetVal = ioctlsocket(sender.m_fd, FIONBIO, &nNonblocking);
		if (nRetVal == SOCKET_ERROR)
			break;
		nOption = 1;
		nRetVal = setsockopt(sender.m_fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&nOption, sizeof(int));
		if (nRetVal == SOCKET_ERROR)
			break;

		nNonblocking = 1;
		nRetVal = ioctlsocket(receiver.m_fd, FIONBIO, &nNonblocking);
		if (nRetVal == SOCKET_ERROR)
			break;
		nOption = 1;
		nRetVal = setsockopt(receiver.m_fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&nOption, sizeof(int));
		if (nRetVal == SOCKET_ERROR)
			break;

		m_fdRecv = receiver.Orphan();
		m_fdSend = sender.Orphan();

		return;
	}	// for (;;)

	//
	// Exception pointer.
	//
	m_fdSend = m_fdRecv = INVALID_SOCKET;
	throw SocketException();	// failed to initializes select interrupter.
}

/**
 * Destroys the BFX::SelectInterrupter instance.
 */
SelectInterrupter::~SelectInterrupter()
{
	if (m_fdSend != INVALID_SOCKET)
		::closesocket(m_fdSend);
	if (m_fdRecv != INVALID_SOCKET)
		::closesocket(m_fdRecv);
}

/**
 * Interrupt the select call.
 * A single byte may be written to this to wake up the select which is waiting for the other end to become readable.
 */
void SelectInterrupter::Interrupt()
{
#ifdef	_WIN32
	char data = 0;
	WSABUF wsaBuffer = { 1, &data };
	DWORD dwBytesSent = 0;
	int nRetVal = ::WSASend(m_fdSend, &wsaBuffer, 1, &dwBytesSent, 0, NULL, NULL);
	BFX_ASSERT(nRetVal != SOCKET_ERROR);
#else	//	_WIN32
	char data = 0;
	iovec bufs;
	bufs.iov_base = &data;
	bufs.iov_len = 1;
	int flags = 0;
	msghdr msg = msghdr();
	msg.msg_iov = &bufs;
	msg.msg_iovlen = 1;
#ifdef	__LINUX__
	flags |= MSG_NOSIGNAL;
#endif	//	__LINUX__
	ssize_t nBytesSent = sendmsg(m_fdSend, &msg, flags);
	BFX_ASSERT(nBytesSent != SOCKET_ERROR);
#endif	//	!_WIN32
}

/**
 * Reset the select interrupt.
 * a single byte will be written on the other end of the connection and this descriptor will become readable.
 *
 * @return True if the call was interrupted.
 */
bool SelectInterrupter::Reset()
{
#ifdef	_WIN32
	char data[1024];
	WSABUF wsaBuffer = { sizeof(data), data };
	DWORD dwBytesReceived = 0;
	DWORD dwFlags = 0;

	int nRetVal = ::WSARecv(m_fdRecv, &wsaBuffer, 1, &dwBytesReceived, &dwFlags, NULL, NULL);
	BFX_ASSERT(nRetVal != SOCKET_ERROR);
	bool bWasInterrupted = dwBytesReceived > 0;

	// clear receive buffer.
	while (dwBytesReceived == sizeof(data))
		::WSARecv(m_fdRecv, &wsaBuffer, 1, &dwBytesReceived, &dwFlags, NULL, NULL);

	return bWasInterrupted;
#else	//	_WIN32
	char data[1024];
	iovec bufs;
	bufs.iov_base = &data;
	bufs.iov_len = sizeof(data);
	msghdr msg = msghdr();
	msg.msg_iov = &bufs;
	msg.msg_iovlen = 1;
	ssize_t nBytesReceived = recvmsg(m_fdRecv, &msg, 0);
	BFX_ASSERT(nBytesReceived != SOCKET_ERROR);
	bool bWasInterrupted = nBytesReceived > 0;
	while (nBytesReceived == sizeof(data))
		recvmsg(m_fdRecv, &msg, 0);
	return bWasInterrupted;
#endif	//	!_WIN32
}

#elif defined(BFX_IO_HAS_EVENTFD)

#else

#endif

} /* namespace BFX */
